#include "Windows/GxWinForm.h"
#include "Windows/GxWinApp.h"
#include "LibGL/GxUI.h"
#include "Windows/GxWinCommonDlg.h"
#include "LibGL/GxTextures.h"
#include "Basic/FileSystem.h"
#include "Regex/MetaLexer.h"
#include "Engine.h"
#include "Lightmap.h"

using namespace GxWinGUI;
using namespace GxRendering::UI;
using namespace GxRendering;
using namespace GxMetaParser;
using namespace FinalRendering::Engine;

class IllumForm : public GLForm
{
private:
	Timer tmrUI;
	RefPtr<UIEntry> ui;
	GxRendering::UI::Label * lblStatus1, * lblStatus2;
	GxRendering::UI::ProgressBar * progress;
protected:
	bool ProcessMessage(GxWinGUI::WinMessage msg)
	{
		BeginGLDrawing();
		GxRendering::UI::Global::MakeCurrent(ui.operator ->());
		bool rs = GLForm::ProcessMessage(msg);
		ui->HandleSystemMessage(msg.hWnd, msg.message, msg.wParam, msg.lParam);
		return rs;
	}
public:
	IllumForm()
	{
		Init();
	}

	~IllumForm()
	{
		Finalize();
	}

	void Init()
	{
		int w = SystemMetrics::GetScreenWidth();
		int h = SystemMetrics::GetScreenHeight();

		bool succ = InitEngine();
		if (!succ)
		{
			MessageBox(L"Engine initialization failed. OpenGL 3.2 is not supported.",
				L"Error", MB_ICONEXCLAMATION);
			exit(0);
		}
		GxRendering::UI::Global::Init();
		_wchdir(FinalRendering::Engine::Global->GetWorkingDirectory().GetStrPtr());

		OpenGL::ClampColor(GL_CLAMP_VERTEX_COLOR_ARB, GL_FALSE);
		OpenGL::ClampColor(GL_CLAMP_READ_COLOR_ARB, GL_FALSE);
		OpenGL::ClampColor(GL_CLAMP_FRAGMENT_COLOR_ARB, GL_FALSE);

		tmrUI.Interval = 50;
		tmrUI.OnTick.Bind(this, &IllumForm::UITimerTick);
		tmrUI.StartTimer();

		ui = new UIEntry(GetClientWidth(), GetClientHeight());
		SetClientWidth(270);
		SetClientHeight(85);
		SetBorder(fbFixedDialog);
		SetText(L"Computing Lightmaps...");
		SetMaximizeBox(false);
		SetMinimizeBox(false);

		SetLeft((w-GetWidth())/2);
		SetTop((h-GetHeight())/2);

		lblStatus1 = new GxRendering::UI::Label();
		lblStatus1->SetText(L"Computing illumination...");
		lblStatus1->Posit(10,10, 80,20);
		ui->AddChild(lblStatus1);
		
		lblStatus2 = new GxRendering::UI::Label();
		lblStatus2->SetText(L"Computing illumination...");
		lblStatus2->Posit(10,30, 80,20);
		ui->AddChild(lblStatus2);

		progress = new GxRendering::UI::ProgressBar();
		progress->Posit(10,50, 180, 20);
		ui->AddChild(progress);

		GxRendering::UI::Button * btn = new GxRendering::UI::Button();
		btn->SetText(L"Cancel");
		btn->Posit(200,50,60,24);
		btn->OnClick.Bind(this, &IllumForm::btnCancelClicked);
		ui->AddChild(btn);
		
		OnClose.Bind(this, &IllumForm::FormClose);

		ready = true;
		finished = false;
	}

	bool finished;

	struct Options
	{
		bool Radiosity;
		float PatchSize;
		float Converge;
		String fileName;
		Options()
		{
			Radiosity = false;
			PatchSize = 32.0f;
			Converge = 0.95f;
		}
	};

	void FormClose(Object * sender, GxWinGUI::WindowCloseEventArgs & e)
	{
		if (!finished)
		{
			e.Cancel = true;
			cancelled = true;
			SetText(L"Cancelling...");
		}
	}

	void ParseOptions(Options & opt)
	{
		String cmdLine = Application::GetCommandLine();
		String profile = 
			L"Ident = {[^\\t\\r\\n\" ]*}\n"\
			L"FileName = {\"[^\"]*\"}\n"\
			L"#WhiteSpace = {\\s+}\n";
		MetaLexer lexer(profile);
		LexStream stream;
		lexer.Parse(cmdLine, stream);
		LexStream::LinkNode * token = stream.Begin();
		if (token)
		{
			opt.fileName = token->Data.Str;
			if (opt.fileName.GetLength()>=2 && opt.fileName[0] == L'\"')
			{
				opt.fileName = opt.fileName.Mid(1, opt.fileName.GetLength()-2);
			}
			token = token->GetNext();
		}
		while (token)
		{
			token->Data.Str.Uppercase();
			if (token->Data.Str == L"-P")
			{
				token = token->GetNext();
				if (token)
				{
					opt.PatchSize = token->Data.Str.ToFloat();
				}
			}
			else if (token->Data.Str == L"-R")
			{
				opt.Radiosity = true;
			}
			else if (token->Data.Str == L"-C")
			{
				token = token->GetNext();
				if (token)
					opt.Converge = token->Data.Str.ToFloat();
			}
			token = token->GetNext();
		}

		if (opt.PatchSize < 1.0f)
			opt.PatchSize = 1.0f;
		if (opt.Converge < 0.5f)
			opt.Converge = 0.5f;
		if (opt.Converge > 0.995f)
			opt.Converge = 0.995f;
	}

	void BindDiffuseTexture(int id)
	{
		Material * mat = renderer->GetMaterial(id);
		if (mat)
		{
			GxRendering::Texture * tex = mat->GetDiffuseTexture();
			if (tex)
			{
				glEnable(GL_TEXTURE_2D);
				tex->Bind();
				return;
			}
		}
		glDisable(GL_TEXTURE_2D);
	}

	RefPtr<Renderer> renderer;
	
	bool cancelled;

	void GenerateLightmap()
	{
		cancelled = false;
		Options opt;
		ParseOptions(opt);
		if (!FileExists(opt.fileName))
		{
			return;
		}
		try
		{
			RefPtr<Scene> scene = new Scene();
			scene->Load(opt.fileName);
			renderer = new Renderer(512,512);
			renderer->SetScene(scene.operator ->());
			RefPtr<LightmapSolution> solution = new LightmapSolution(scene.operator ->());
			solution->ProgressChanged.Bind(this, &IllumForm::ProgressChanged);
			solution->StatusChanged.Bind(this, &IllumForm::StatusChanged);
			solution->ComputeRadiosity = opt.Radiosity;
			solution->RadiosityThreshold = 1.0f - opt.Converge;
			solution->PatchSize = opt.PatchSize;
			solution->BindDiffuseTexture.Bind(this, &IllumForm::BindDiffuseTexture);
			try
			{
				solution->Generate();
				StatusChanged(L"Saving...",L"Encoding...");
				String elemFileName = ExtractFilePath(opt.fileName) + ExtractFileNameWithoutEXT(opt.fileName) + L"_illum";
				String rsFileName = elemFileName + L".gxilm";
				solution->Save(rsFileName);
				scene->Save(elemFileName + L".gxbsp");
			}
			catch (CancelException)
			{
				return;
			}
			catch (LightmapErrorException)
			{
				MessageBox(L"Error occured while computing lightmaps.", L"Error", MB_ICONEXCLAMATION);
			}
		}
		catch (...)
		{
			MessageBox(L"Illegal working directory.", L"Error", MB_ICONEXCLAMATION);
		}
	}

	class CancelException
	{};

	void ProgressChanged(int val, int max)
	{
		glPushAttrib(GL_ALL_ATTRIB_BITS);
		progress->SetMax(max);
		progress->SetPosition(val);
		Application::DoEvents();
		glPopAttrib();
		if (cancelled)
			throw CancelException();
	}

	void StatusChanged(String s1, String s2)
	{
		glPushAttrib(GL_ALL_ATTRIB_BITS);
		lblStatus1->SetText(s1);
		lblStatus2->SetText(s2);
		Application::DoEvents();
		glPopAttrib();
		if (cancelled)
			throw CancelException();
	}

	void btnCancelClicked(UI_Base * sender)
	{
		cancelled = true;
	}


	void Finalize()
	{
		GxRendering::UI::Global::Finalize();
		FinalizeEngine();
	}

	bool ready;

	void UITimerTick(Object * sender, GxWinGUI::EventArgs e)
	{
		BeginGLDrawing();
		Global::MakeCurrent(ui.operator ->());
		Shader::UseProgramObjectARB(0);
		FBO::BindFramebuffer(GL_FRAMEBUFFER, 0);
		MultiTexture::ActiveTexture(GL_TEXTURE0);
		glViewport(0,0, GetClientWidth(), GetClientHeight());
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
		ui->DrawUI();
		SwapBuffers(wglGetCurrentDC());
		if (finished)
			Close();
		else if (ready)
		{
			ready = false;
			GenerateLightmap();
			finished = true;
		}
	}


};

int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nShowCmd)
{
	Application::Init(hInstance, hPrevInstance, lpCmdLine, nShowCmd);
	Application::Run(new IllumForm());
	Application::Dispose();
	return 1;
}